package itsecurity.collision;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import utils.BitOperator;

import iaik.asn1.ObjectID;
import iaik.asn1.structures.AlgorithmID;
import iaik.asn1.structures.Name;
import iaik.pkcs.pkcs7.DigestInfo;
import iaik.security.provider.IAIK;
import iaik.x509.X509Certificate;
import itsecurity.cert.CertificateFactory;
import itsecurity.cert.KeyManager;
import itsecurity.cert.MDBProvider;
import itsecurity.cert.SaveCertToFile;
import itsecurity.hash.DaviesMeyerDES;
import itsecurity.hash.roque.DaviesMeyerDESRoque;

/**
 * Implements collision finding related functions
 *
 */
public class CollisionFinder {
	private byte[] tortoise, hare;
	private X509Certificate origCert;
	private X509Certificate roqueCert;
	
	/**
	 * First iv to use in birthday bit finding algoritgm. Initialized with testing values.
	 */
	byte[] iv1 = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
			(byte) 0x93, (byte) 0x18, (byte) 0x90};
	byte[] iv2 = {(byte) 0x00, (byte) 0x40, (byte) 0x00, (byte) 0x05, (byte) 0xFF, 
			(byte) 0x93, (byte) 0x18, (byte) 0x90};
	
	private int mu=0, lam=1;

	/**
	 * The value of tortoise before collision, i.e. birthday bits
	 */
	private byte[] lastTortoise = new byte[8];
	private byte[] lastHare = new byte[8];
	
	public CollisionFinder() {
//	try {
//		findCycle();
//	} catch (Exception e) {
//		// TODO Auto-generated catch block
//		e.printStackTrace();
//	}
	}
	
	/**
	 * Initialize IVs for birthday bit finding algorithm
	 * @param iv1
	 * @param iv2
	 */
	public CollisionFinder(byte[] iv1, byte[] iv2) {
		this.iv1=iv1;
		this.iv2=iv2;
	}
	
	/**
	 * Uses Brent's cycle-detection algorithm, which is a more efficient
	 * variation of Floyd's algorithm. 
	 * TODO: not working...
	 * @throws Exception
	 */
	public void findCycle() throws Exception {
				
		Date start = new Date();
		tortoise = new byte[7];
		hare = new byte[7];
		
		int power = lam = 1;
		
		byte [] tmp;
		tortoise = new byte[7];
		tmp = hash(iv1,tortoise);
		System.arraycopy(tmp, 0, hare, 0, 7);		
		
		// finding lambda
		while (!Arrays.equals(tortoise, hare)) {
			//start new power of two?
			if (power==lam) {
//				tortoise = hare;
				System.arraycopy(hare, 0, tortoise, 0, 7);
				power *= 2;
				lam=0;
			}

			if(BitOperator.getBit(hare, 7) == 0)
			{
				tmp = hash(iv1,hare);
				System.arraycopy(tmp, 0, hare, 0, 7);
				
			}
			else
			{
				tmp = hash(iv2,hare);
				System.arraycopy(tmp, 0, hare, 0, 7);
			}
			lam++;
		}
		
		Date end = new Date();
		long ms = end.getTime()-start.getTime();
		System.out.println("\n\nTime elapsed with 1st loop: "+ 
				String.format("%d min, %d sec", 
		    TimeUnit.MILLISECONDS.toMinutes(ms),
		    TimeUnit.MILLISECONDS.toSeconds(ms) - 
		    TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(ms))
				)
		);
		
	// finding my
		hare = new byte[7];
		tortoise = new byte[7];
		for (int i = 0; i < lam; i++) {
			if(BitOperator.getBit(hare, 7) == 0)
			{
				tmp = hash(iv1,hare);
				System.arraycopy(tmp, 0, hare, 0, 7);
			}
			else
			{
				tmp = hash(iv2,hare);
				System.arraycopy(tmp, 0, hare, 0, 7);
			}
		}

		mu=0;
		while (!Arrays.equals(tortoise, hare)) {				
			System.arraycopy(hare, 0, lastHare, 0, 7);
			System.arraycopy(tortoise, 0, lastTortoise, 0, 7);
			
		// tortoise moves
			if(BitOperator.getBit(tortoise, 7) == 0)
			{
				tmp = hash(iv1,tortoise);
				System.arraycopy(tmp, 0, tortoise, 0, 7);
			}
			else
			{
				tmp = hash(iv2,tortoise);
				System.arraycopy(tmp, 0, tortoise, 0, 7);
			}
			// hare moves
			if(BitOperator.getBit(hare, 7) == 0)
			{
				tmp = hash(iv1,hare);
				System.arraycopy(tmp, 0, hare, 0, 7);				
			}
			else
			{
				tmp = hash(iv2,hare);
				System.arraycopy(tmp, 0, hare, 0, 7);
			}
			mu++;
		}

		end = new Date();
		ms = end.getTime()-start.getTime();
		System.out.println("\nTime elapsed in total: "+ 
				String.format("%d min, %d sec", 
		    TimeUnit.MILLISECONDS.toMinutes(ms),
		    TimeUnit.MILLISECONDS.toSeconds(ms) - 
		    TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(ms))
				)
		);
		
		System.out.println("\nWith iv1 " + iaik.utils.Util.toString(iv1) +
				"\nand iv2 "+ iaik.utils.Util.toString(iv2) + 
				"\nfound birthday bits: \n" + iaik.utils.Util.toString(lastTortoise)+
				" and\n "+iaik.utils.Util.toString(lastHare)+
				"\nHash with IV1 for bdbits1: " + hash(iv1,lastTortoise)+
				"\nand eith IV2 for bdbits2: " + hash(iv2,lastHare));
	
		System.out.println("mu: "+mu+", lam: "+lam);
	}
	
	public byte[] hash( byte[] iv, byte[] m) throws Exception {
		m = addParityBits(m);
		//System.out.println(iaik.utils.Util.toString(input));
		//byte[] orig = input;
		//input = BitOperator.doXOR(itsecurity.hash.DES.hash(input, key), orig);
		m = BitOperator.doXOR(itsecurity.hash.DES.encrypt(iv, m), iv);
		m[7]=(byte)0;
		return m;
	}
	
	private byte[] addParityBits(byte[] input) {
		byte[] parities = new byte[8];
		for (int i=0;i<=7;i++) {
			int ones=0;
			for (int j=0;j<7;j++) {
				int bit = BitOperator.getBit(input, i*7+j);
				if (bit>0) {
					parities=BitOperator.setBit(parities, i*8+j, bit);
					ones++;
				}
			}
			if (ones%2==0)
				parities=BitOperator.setBit(parities, 8*i+7, 1);
//			else
//				parities=BitOperator.setBit(parities, 8*(i+1)-1, 1);
		}
		return parities;
	}
	
	public byte[] getLastTortoise() {return lastTortoise;}
	
	public byte[] getLastHare() {return lastHare;}
	
	public void createCertTemplates() throws IOException {
		
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		
		Name subject = new Name();
		Name subject2 = new Name();
	  Name issuer = new Name();
	  Name issuer2 = new Name();

	  subject.addRDN(ObjectID.commonName ,"Bob Truehart");
	  subject.addRDN(ObjectID.country, "AT");
	  subject.addRDN(ObjectID.locality, "Graz");
	  subject.addRDN(ObjectID.organization ,"TU Graz");
	  subject.addRDN(ObjectID.organizationalUnit ,"IAIK");
	  
    issuer.addRDN(ObjectID.country, "FIN");
    issuer.addRDN(ObjectID.locality, "Oulu");
    issuer.addRDN(ObjectID.organization ,"University of Oulu");
    issuer.addRDN(ObjectID.commonName ,"Circus Authorities");
	  
    subject2.addRDN(ObjectID.commonName ,"Eve L. Witcher");
    subject2.addRDN(ObjectID.country, "Narnia");
    subject2.addRDN(ObjectID.locality, "Winterlands");
    subject2.addRDN(ObjectID.organization ,"Witchschool");
	  subject2.addRDN(ObjectID.organizationalUnit ,"Crackers");

    issuer2.addRDN(ObjectID.country, "Narnia");
    issuer2.addRDN(ObjectID.locality, "Winterlands");
    issuer2.addRDN(ObjectID.organization ,"Witchschool");
    issuer2.addRDN(ObjectID.commonName ,"Eve L. Witcher");
    
		KeyPair kp = KeyManager.generateRSAKeyPair();
//		KeyPair kp2 = KeyManager.generateRSAKeyPair();
//		char [] password = in.readLine().toCharArray();
		File file=new File(KeyManager.KEYSTORE);
//		file.createNewFile();
   	PrivateKey CAKey = KeyManager.getKeyPair("Circus Authorities", "Bob".toCharArray(), file).getPrivate();

   	AlgorithmID algorithm = new AlgorithmID("1.3.6.1.4.1.2706.4.1.2", "mdbWithRSAEncryption");

   	byte [] pubBytes = new byte[7];
//   	System.arraycopy(kp.getPublic().getEncoded(), 0, pubBytes, 0, 8);
   	
   	try {
			origCert = CertificateFactory.createCertificate(subject, kp.getPublic(), issuer, 
					CAKey, algorithm, 4, null);
			roqueCert = CertificateFactory.createCertificate(subject2, kp.getPublic(), issuer2, 
					CAKey, algorithm, 5, null);
   	} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//hashAndSign(pubBytes);
//		Certificate[] certs = {origCert, rogueCert};
		SaveCertToFile.saveCertificate(origCert, "TrueCert.cer");
		SaveCertToFile.saveCertificate(roqueCert, "RoqueCert.cer");
		
		//KeyManager.saveCert(alias, password, CAKey, certs, file);
	}

	/**
	 * Sends certs to DaviesMeyerDesRoque object, who counts the last IHVs,
	 * without padding, and returns the computing to an object of this class,
	 * who counts the birthday bits.
	 * 
	 */
	private void hashAndPrintBdayBits(byte[] identPublicKey) {
		DaviesMeyerDESRoque digester = new DaviesMeyerDESRoque();
		byte[] hash = null;
		byte[] hash2 = null;
		try {
			digester.doHash(origCert.toByteArray(), roqueCert.toByteArray());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
	
	public static void main (String[] args) {
		try {
      Security.insertProviderAt(new IAIK(), 2);
      Security.insertProviderAt(new MDBProvider(), 3);
    } catch( Throwable e ) {
      //
    }
		try {
			CollisionFinder cf = new CollisionFinder();
			cf.createCertTemplates();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
